package com.lam.common.validate;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Email;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;

import com.lam.common.components.dict.DictUtil;
import com.lam.common.components.dict.annotation.Dict;
import com.lam.common.components.dict.constant.DictConstant;
import com.lam.common.components.dict.model.DictDesc;
import com.lam.common.exception.BaseException;
import com.lam.common.utils.BeanUtil;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.StringUtils;
import com.lam.common.validate.annotation.DictValid;
import org.hibernate.validator.constraints.Length;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.databind.ser.std.StdKeySerializers.Default;

public class ValidConvert {
	
	private static final Logger logger = LoggerFactory.getLogger(ValidConvert.class);
	
	/**
	 * 根据导入的Java类，解析出Excel下拉列表数据，根据类属性上面的<link>Dict</link>和<link>DictValid</link>注解进行解析
	 * @param importDataClazz 接收导入数据的Java类
	 * @param groups 字段校验的分组
	 * @return
	 */
	public static Map<Integer, String[]> getDropDownData(Class<?> importDataClazz, Class<?> ... groups){
		
		List<Class<?>> groupList = new ArrayList<>(Arrays.asList(groups));
		if(CollectionUtil.isEmpty(groupList)) {
			groupList = Collections.singletonList(Default.class);
		}else {
			if(!groupList.contains(Default.class)) {
				groupList.add(Default.class);
			}
		}
		
		Map<Field, Dict> fieldDictData = BeanUtil.getFieldAnnotation(importDataClazz, Dict.class, BeanUtil.METHOD_TYPE_GETTER);
		Map<Field, DictValid> fieldValidData = BeanUtil.getFieldAnnotation(importDataClazz, DictValid.class, BeanUtil.METHOD_TYPE_GETTER);
		if (CollectionUtil.isEmpty(fieldDictData) || CollectionUtil.isEmpty(fieldValidData)) {
			return Collections.emptyMap();
		}
		Map<Field, ExcelProperty> excelPropertyData = BeanUtil.getFieldAnnotation(importDataClazz, ExcelProperty.class);
		if (CollectionUtil.isEmpty(excelPropertyData)) {
			return Collections.emptyMap();
		}
		
		Map<Integer, String[]> dropDownData = new HashMap<>();
		Map<String, Map<String, String>> dictMapping = new HashMap<>();
		Dict dict;
		DictValid dictValid;
		String key;
		Field field;
		int i = -1;
		for (Entry<Field, ExcelProperty> entry : excelPropertyData.entrySet()) {
			i++;
			field = entry.getKey();
			dictValid = fieldValidData.get(field);
			dict = fieldDictData.get(field);
			if(dictValid == null || dict == null || !groupContains(dictValid.groups(), groupList)) {
				continue;
			}
			
			key = DictUtil.uniqueKey(dict);
			Map<String, String> dictMap = dictMapping.get(key);
			DictDesc dictDesc = new DictDesc(field.getName(), dict);
			if (CollectionUtil.isEmpty(dictMap)) {
				dictMap = DictUtil.getDictInfo(dictDesc);
				dictMapping.put(key, dictMap);
			}
			Collection<String> vs = dictMap.keySet();
			dropDownData.put(i, vs.toArray(new String[vs.size()]));
		}
		return dropDownData;		
	}
	
	/**
	 * 根据接收导入数据的Java类和保存到数据库实体类，解析出导入模板的表头批注，字段名从 importDataClazz 中有 ExcelProperty 注解的字段获取，批注内容从 entityClazz 中有javax.validate验证注解的字段获取
	 * @param importDataClazz 接收导入数据的Java类
	 * @param entityClazz 保存到数据库实体类
	 * @param groups 字段校验的分组
	 * @return
	 */
	public static Map<Integer, String> getCommentData(Class<?> importDataClazz, Class<?> entityClazz, Class<?> ... groups){
		
		Map<Field, ExcelProperty> excelPropertyData = BeanUtil.getFieldAnnotation(importDataClazz, ExcelProperty.class);
		if (CollectionUtil.isEmpty(excelPropertyData)) {
			return Collections.emptyMap();
		}
		
		List<Class<?>> groupList = new ArrayList<>(Arrays.asList(groups));
		if(CollectionUtil.isEmpty(groupList)) {
			groupList = Collections.singletonList(Default.class);
		}else {
			if(!groupList.contains(Default.class)) {
				groupList.add(Default.class);
			}
		}
		
		Set<Class<?>> validAnnos = getValidAnnos();
		Map<Integer, String> commentData = new LinkedHashMap<>(); 
		List<Field> fieldList = BeanUtil.getBusinessFields(entityClazz);
		Map<String, Field> fieldMap = CollectionUtil.getMap(fieldList, t->{
			Dict dict = t.getAnnotation(Dict.class);
			if(dict == null) {
				return t.getName();
			}
			if(StringUtils.isNotBlank(dict.targetField())) {
				return dict.targetField();
			}
			return dict.reverse() ? t.getName().replace(DictConstant.DICT_TEXT_SUFFIX, "") 
					: t.getName() + DictConstant.DICT_TEXT_SUFFIX;
		});
		//Map<String, Field> fieldMap = CollectionUtil.getMap(fieldList, Field::getName);
		int i = -1;
		String comment;
		for (Entry<Field, ExcelProperty> entry : excelPropertyData.entrySet()) {
			i++;
			if(fieldMap.containsKey(entry.getKey().getName())) {
				comment = getCommentData(entityClazz, fieldMap.get(entry.getKey().getName()), groupList, validAnnos);
				if(StringUtils.isNotBlank(comment)) {
					commentData.put(i, comment);
				}
			}
		}
		return commentData;
	}
	
	private static String getCommentData(Class<?> clazz, Field field, List<Class<?>> groupList, Set<Class<?>> validAnnos) {
		
		try {
			Annotation[] ass = field.getAnnotations();
			Annotation[] getterAss = new PropertyDescriptor(field.getName(), clazz).getReadMethod().getAnnotations();
			Set<Annotation> annoSet = new HashSet<>();
			for (Annotation annotation : getterAss) {
				if(groupContains(annotation, validAnnos, groupList)) {
					annoSet.add(annotation);
				}
			}
			for (Annotation annotation : ass) {
				if(groupContains(annotation, validAnnos, groupList)) {
					annoSet.add(annotation);
				}
			}
			if(annoSet.isEmpty()) {
				return null;
			}
			
			Set<String> commentSet = new LinkedHashSet<>();
			annoSet.stream().forEach(t->getValidAnnotations(t, commentSet));
			return StringUtils.list2Str(commentSet, "，");
			
		} catch (IntrospectionException e) {
			logger.error("获取批注数据异常", e);
			throw new BaseException("生成批注信息异常");
		}
	}
	
	private static Set<Class<?>> getValidAnnos(){
		return new HashSet<>(Arrays.asList(NotNull.class, NotBlank.class, NotEmpty.class, Size.class, Length.class, Email.class, Min.class, 
				Max.class, DecimalMin.class, DecimalMax.class, AssertTrue.class, AssertFalse.class, Digits.class, Past.class, Future.class, DictValid.class));
	}
	
	private static void getValidAnnotations(Annotation annotation, Set<String> commentSet){
		Class<?> clazz = annotation.annotationType();
		if(clazz == NotNull.class || clazz == NotBlank.class || clazz == NotEmpty.class) {
			commentSet.add("必填");
		}else if(clazz == Size.class) {
			commentSet.add(getComment("大小在{0}-{1}", annotation, "min", "max"));
		}else if(clazz == Length.class) {
			commentSet.add(getComment("长度在{0}-{1}", annotation, "min", "max"));
		}else if(clazz == Email.class) {
			commentSet.add("必须是邮箱地址");
		}else if(clazz == DictValid.class) {
			commentSet.add("值从下拉选项中选择");
		}else if(clazz == Min.class) {
			commentSet.add("必须是数字");
			commentSet.add(getComment("必须>={0}", annotation, "value"));
		}else if(clazz == Max.class) {
			commentSet.add("必须是数字");
			commentSet.add(getComment("必须<={0}", annotation, "value"));
		}else if(clazz == DecimalMin.class) {
			commentSet.add("必须是数字");
			if((boolean) invokeAnno(annotation, "inclusive")) {
				commentSet.add(getComment("必须>={1}", annotation, "value"));
			}else {
				commentSet.add(getComment("必须>{1}", annotation, "value"));
			}
		}else if(clazz == DecimalMax.class) {
			commentSet.add("必须是数字");
			if((boolean) invokeAnno(annotation, "inclusive")) {
				commentSet.add(getComment("必须<={1}", annotation, "value"));
			}else {
				commentSet.add(getComment("必须<{1}", annotation, "value"));
			}
		}else if(clazz == AssertTrue.class) {
			commentSet.add("必须为true");
		}else if(clazz == AssertFalse.class) {
			commentSet.add("必须为false");
		}else if(clazz == Digits.class) {
			commentSet.add("必须是数字");
			commentSet.add(getComment("整数位数不大于{0}", annotation, "integer"));
			commentSet.add(getComment("小数位数不大于{0}", annotation, "fraction"));
		}else if(clazz == Past.class) {
			commentSet.add("必须是过去的日期");
		}else if(clazz == Future.class) {
			commentSet.add("必须是未来的日期");
		}
	}
	
	private static String getComment(String pattern, Annotation annotation, String ... props) {
		Object[] arr = new Object[props.length];
		int i = 0;
		for (String prop : props) {
			arr[i++] = invokeAnno(annotation, prop);
		}
		return MessageFormat.format(pattern, arr);
	}
	
	private static Object invokeAnno(Annotation annotation, String prop) {
		try {
			return annotation.annotationType().getMethod(prop).invoke(annotation);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException
				| SecurityException e) {
			logger.error("从注解获取批注数据异常", e);
			throw new BaseException("生成批注信息异常");
		}
	}
	
	/**
	 * 判断分组列表中是否存在指定分组，只要groups中包含 groupList中的一个，或groupList中的任意一个是groups中的子类，则返回True，否则返回false
	 * @param groupList 分组列表
	 * @param groups 指定分组， 默认为Default分组
	 * @return
	 */
	public static boolean groupContains(Class<?>[] groups, List<Class<?>> groupList) {
		
		if(CollectionUtil.isEmpty(groupList)) {
			groupList = Collections.singletonList(Default.class);
		}
		
		if(CollectionUtil.isEmpty(groups)) {
			return groupList.contains(Default.class);
		}
		
		for (Class<?> cls : groupList) {
			for (Class<?> groupCls : groups) {
				if(groupCls.isAssignableFrom(cls)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 判断分组列表中是否存在指定验证注解对应的分组，只要annotation的groups中包含 groupList中的一个，或groupList中的任意一个是groups中的子类，则返回True，否则返回false
	 * @param groupList 分组列表 
	 * @param annotation 验证注解对象
	 * @param validAnnos 指定验证注解的范围，支持的验证注解才进行判断
	 * @return
	 */
	public static boolean groupContains(Annotation annotation, Set<Class<?>> validAnnos, List<Class<?>> groupList) {
		
		if(!validAnnos.contains(annotation.annotationType())) {
			return false;
		}
		
		Class<?>[] groups = (Class<?>[])invokeAnno(annotation, "groups");
		
		return groupContains(groups, groupList);
	}
	
}
